#pragma once

#include "NamedPipeServer.h"
#include "specialedition.h"
const std::string MOUSEPIPENAME = ("\\\\.\\pipe\\DxMousePipe");
/*
Name = \\.\\pipe\\DInput-Hook
\\.
	Local computer.

\\pipe\\
	Not a physical file path, but rather a pipe

DInput-Hook
	Pipe name.
*/
//const TCHAR* FULL_PIPE_NAME = _T("\\\\.\\pipe\\DInput-Hook");


class MouseDevice : public IDirectInputDevice8
{
public:
	MouseDevice(IDirectInputDevice8* device) : m_device(device), m_serverPipe(new NamedPipeServer(MOUSEPIPENAME) )
	{			
	}

	/*** IUnknown methods ***/
	STDMETHOD(QueryInterface)(REFIID riid, void** ppvObj)
	{
		return m_device->QueryInterface(riid, ppvObj);
	}

	STDMETHOD_(ULONG,AddRef)()
	{
		return m_device->AddRef();
	}

	STDMETHOD_(ULONG,Release)()
	{
		ULONG count = m_device->Release();
		if(0 == count)
			delete this;

		return count;
	}

	/*** IDirectInputDevice8 methods ***/
	STDMETHOD(GetCapabilities)(LPDIDEVCAPS devCaps)
	{
		return m_device->GetCapabilities(devCaps);
	}

	STDMETHOD(EnumObjects)(LPDIENUMDEVICEOBJECTSCALLBACK callback, LPVOID ref, DWORD flags)	
	{
		return m_device->EnumObjects(callback, ref, flags);
	}

	STDMETHOD(GetProperty)(REFGUID rguid, LPDIPROPHEADER ph)
	{
		return m_device->GetProperty(rguid, ph);
	}

	STDMETHOD(SetProperty)(REFGUID rguid, LPCDIPROPHEADER ph)
	{		
		return m_device->SetProperty(rguid, ph);

		/*
		You can detect immediate/buffered modes as such:
		
		HRESULT hr = m_device->SetProperty(rguid, ph);

		if(SUCCEEDED(hr) && rguid == DIPROP_BUFFERSIZE)
		{
			DWORD data = *reinterpret_cast<const DWORD*>(ph + 1);
			m_is_immediate = (data == 0);
		}

		return hr;
		*/
	}

	STDMETHOD(Acquire)()
	{
		 HRESULT hr=m_device->Acquire();
		 
		 return DI_OK;
	}

	STDMETHOD(Unacquire)()
	{
		return m_device->Unacquire();
	}

	STDMETHOD(GetDeviceState)(DWORD size, LPVOID data)
	{
		HRESULT hr = m_device->GetDeviceState(size, data);
		static BOOL mMouseClicked=FALSE;		

		BOOL DataReceived=FALSE;
		//TODO: support a lot of DIMOUSESTATE
		//(Custom mouse data type currently not supported)

		//if(SUCCEEDED(hr) && size == sizeof(DIMOUSESTATE))
		if(size == sizeof(DIMOUSESTATE))
		{		
			DIMOUSESTATE* keys = static_cast<DIMOUSESTATE*>(data);	
			if(FALSE != Locked){
				char* pdata=reinterpret_cast<char*>(data);
				for(auto i=0; i<sizeof(DIMOUSESTATE);++i){
					pdata[i]=0;
				}
			}


			if(TRUE == mMouseClicked){
				mMouseClicked=FALSE;
				DataReceived=TRUE;
			}else if(m_serverPipe->IsDataAvailable())	{
				std::string message;
				if(m_serverPipe->Read(message))
				{
					std::stringstream ss(message);
					unsigned char ch;
						
					while(ss>> ch) {						
						switch(ch){
							case 'L':
								keys->rgbButtons[0]|=(0x80);
								mMouseClicked=TRUE;
								break;
							case 'R':
								keys->rgbButtons[2]|=(0x80);
								mMouseClicked=TRUE;
								break;
							default:
								MessageBoxA(0,"OAO?","OAO!!!!!!!",MB_OK);
								break;
						}
					}
					DataReceived=TRUE;
				}		
			}
			//pipe recreation 
			if(TRUE == m_serverPipe->IsBroken()){
				delete m_serverPipe;
				m_serverPipe = new NamedPipeServer(MOUSEPIPENAME);
			}

			if( DIERR_NOTACQUIRED == hr ) {
				while( DIERR_INPUTLOST ==  m_device->Acquire()) {}
			}				
		}
		
			
		 // we are in background


			// at this time, mouse is unacquired
			//fiddle something
		// struct _DIMOUSESTATE {
		// 	LONG    lX;
		// 	LONG    lY;
		// 	LONG    lZ;
		// 	BYTE    rgbButtons[4];
		// };

		//	return DI_OK;
			
		// TODO: switchable acquire state
		if(TRUE == DataReceived){
			return DI_OK;
		}
		return DI_OK;
	}

	STDMETHOD(GetDeviceData)(DWORD size, LPDIDEVICEOBJECTDATA data, LPDWORD numElements, DWORD flags)
	{
		HRESULT hr= m_device->GetDeviceData(size, data, numElements, flags);
		
		return hr;
	}

	STDMETHOD(SetDataFormat)(LPCDIDATAFORMAT df)
	{		
		return m_device->SetDataFormat(df);
	}

	STDMETHOD(SetEventNotification)(HANDLE event)
	{
		return m_device->SetEventNotification(event);
	}

	STDMETHOD(SetCooperativeLevel)(HWND window, DWORD level)
	{
		
		return m_device->SetCooperativeLevel(window, DISCL_BACKGROUND|DISCL_NONEXCLUSIVE);
		return m_device->SetCooperativeLevel(window, level);
	}

	STDMETHOD(GetObjectInfo)(LPDIDEVICEOBJECTINSTANCE object, DWORD objId, DWORD objHow)
	{
		return m_device->GetObjectInfo(object, objId, objHow);
	}

	STDMETHOD(GetDeviceInfo)(LPDIDEVICEINSTANCE di)
	{
		return m_device->GetDeviceInfo(di);
	}

	STDMETHOD(RunControlPanel)(HWND owner, DWORD flags)
	{
		return m_device->RunControlPanel(owner, flags);
	}

	STDMETHOD(Initialize)(HINSTANCE instance, DWORD version, REFGUID rguid)
	{
		return m_device->Initialize(instance, version, rguid);
	}

	STDMETHOD(CreateEffect)(REFGUID rguid, LPCDIEFFECT effect_params, LPDIRECTINPUTEFFECT* effect, LPUNKNOWN unknown)
	{
		return m_device->CreateEffect(rguid, effect_params, effect, unknown);
	}

    STDMETHOD(EnumEffects)(LPDIENUMEFFECTSCALLBACK callback, LPVOID ref, DWORD type)
	{
		return m_device->EnumEffects(callback, ref, type);
	}

    STDMETHOD(GetEffectInfo)(LPDIEFFECTINFO effect_info, REFGUID rguid)
	{
		return m_device->GetEffectInfo(effect_info, rguid);
	}

    STDMETHOD(GetForceFeedbackState)(LPDWORD state)
	{
		return m_device->GetForceFeedbackState(state);
	}

    STDMETHOD(SendForceFeedbackCommand)(DWORD flags)
	{
		return m_device->SendForceFeedbackCommand(flags);
	}

    STDMETHOD(EnumCreatedEffectObjects)(LPDIENUMCREATEDEFFECTOBJECTSCALLBACK callback, LPVOID ref, DWORD flags)
	{
		return m_device->EnumCreatedEffectObjects(callback, ref, flags);
	}

    STDMETHOD(Escape)(LPDIEFFESCAPE escape)
	{
		return m_device->Escape(escape);
	}

    STDMETHOD(Poll)()
	{
		//Mouse will need be poll before getdata
		HRESULT hr= m_device->Poll();
		
			//do sth
			//todo: .bad
			//remember when failed
			// and make process reacquire it again
			//return DI_OK;
		return hr;

	}

	STDMETHOD(SendDeviceData)(DWORD size, LPCDIDEVICEOBJECTDATA data, LPDWORD num_elements, DWORD flags)
	{
		return m_device->SendDeviceData(size, data, num_elements, flags);
	}

	STDMETHOD(EnumEffectsInFile)(LPCTSTR file_name, LPDIENUMEFFECTSINFILECALLBACK callback, LPVOID ref, DWORD flags)
	{
		return m_device->EnumEffectsInFile(file_name, callback, ref, flags);
	}

    STDMETHOD(WriteEffectToFile)(LPCTSTR file_name, DWORD num_entries, LPDIFILEEFFECT effects, DWORD flags)
	{
		return m_device->WriteEffectToFile(file_name, num_entries, effects, flags);
	}

    STDMETHOD(BuildActionMap)(LPDIACTIONFORMAT format, LPCTSTR username, DWORD flags)
	{
		return m_device->BuildActionMap(format, username, flags);
	}

    STDMETHOD(SetActionMap)(LPDIACTIONFORMAT format, LPCTSTR username, DWORD flags)
	{
		return m_device->SetActionMap(format, username, flags);
	}

    STDMETHOD(GetImageInfo)(LPDIDEVICEIMAGEINFOHEADER image_header)
	{
		return m_device->GetImageInfo(image_header);
	}

private:
	IDirectInputDevice8* m_device;	
	NamedPipeServer* m_serverPipe;	
};